#include "library/libmysql.h"
#include "mysql.h"
#include "mysqld_error.h"
#include <assert.h>
#include <stdio.h>
#include <iostream>
#include <apr_strings.h>
#include <cstdlib>

using namespace basic::library2;

CMySqlResultSet::CMySqlResultSet(MYSQL_RES *m_pstRusultSet) :m_pstRusultSet(m_pstRusultSet)
{
    memset(m_szErrMsg, 0, sizeof (m_szErrMsg));
    m_stCurrRow = NULL;
    this->m_dwFieldCount = mysql_num_fields(m_pstRusultSet);
    this->m_dwRowCount   = mysql_num_rows(m_pstRusultSet);

    MYSQL_FIELD *fields = mysql_fetch_fields(m_pstRusultSet);
    m_vOffsetToFieldName.clear();
    for (apr_int32_t i = 0; i < this->m_dwFieldCount; ++i)
    {
        m_vFieldNameToOffset[fields[i].name] = i;
        m_vOffsetToFieldName.push_back(fields[i].name);
    }
}

CMySqlResultSet::~CMySqlResultSet()
{
    Close();
}

const char* CMySqlResultSet::GetLastErrMsg() const
{
    return m_szErrMsg;
}

void CMySqlResultSet::Close()
{
    mysql_free_result(m_pstRusultSet);
    m_pstRusultSet = NULL;
}

apr_int32_t CMySqlResultSet::GetColumnCount()
{
    assert(m_pstRusultSet);
    return m_dwFieldCount;
}

apr_int64_t CMySqlResultSet::GetRecordCount()
{
    assert(m_pstRusultSet);
    return m_dwRowCount;
}

bool CMySqlResultSet::Next()
{
    assert(m_pstRusultSet);
    m_stCurrRow = mysql_fetch_row(m_pstRusultSet);
    
    return (m_stCurrRow != NULL);
}


const char* CMySqlResultSet::GetField(apr_uint32_t dwFieldIndex)
{
    assert(m_pstRusultSet);
    assert(m_stCurrRow);
    return m_stCurrRow[dwFieldIndex];
}


const char*  CMySqlResultSet::GetField(const char *psFieldName)
{
    assert(m_pstRusultSet);
    assert(m_stCurrRow);
    if (m_vFieldNameToOffset.find(psFieldName)!=m_vFieldNameToOffset.end())
    {
        apr_int32_t dwOffset = m_vFieldNameToOffset[psFieldName];
        return m_stCurrRow[dwOffset];
    }
    return NULL;
}

const char *CMySqlResultSet::GetFieldName(apr_int32_t offset)
{
    assert(m_pstRusultSet);
    assert(m_stCurrRow);
    return m_vOffsetToFieldName[offset].c_str();
}

CMySqlConnection::CMySqlConnection()
{
    memset(m_szDBHost, 0, sizeof (m_szDBHost));
    memset(m_szUserName, 0, sizeof (m_szUserName));
    memset(m_szPassword, 0, sizeof (m_szPassword));
    memset(m_szDBName, 0, sizeof (m_szDBName));
    memset(m_szErrMsg, 0, sizeof (m_szErrMsg));
    memset(m_szCharsetName, 0, sizeof (m_szCharsetName));
    m_dwPort = 0;

    m_bConnected = false;

    m_pstDBLink = (MYSQL *)malloc(sizeof(MYSQL));
    memset(m_pstDBLink, 0, sizeof (MYSQL));
    m_dwWaitTimeOut = 0;
	mysql_init(m_pstDBLink);
}

CMySqlConnection::~CMySqlConnection()
{
    Close();
    free(m_pstDBLink);
}

bool CMySqlConnection::ReConnectDB()
{
    apr_int32_t i = 0;

    while (!m_bConnected && (i++ < 3))
    {
        m_bConnected = Connect();
    }

    return m_bConnected;
}

bool CMySqlConnection::Connect()
{
    assert(!m_bConnected);
    if (m_szCharsetName[0] != 0)
    {
        mysql_options(m_pstDBLink, MYSQL_SET_CHARSET_NAME, m_szCharsetName);
    }
    if (mysql_real_connect(m_pstDBLink, m_szDBHost,
                            m_szUserName, m_szPassword, m_szDBName, m_dwPort, 0 ,0) == NULL)
    {
        apr_snprintf(m_szErrMsg, sizeof(m_szErrMsg), "Fail To Connect To Mysql: %s", mysql_error(m_pstDBLink));
        return false;
    }
    if (m_dwWaitTimeOut > 0)
    {
        char pszSQL[80];
        apr_snprintf(pszSQL, sizeof(pszSQL), "set wait_timeout=%d;", m_dwWaitTimeOut);
        apr_int32_t iRetCode = mysql_real_query(m_pstDBLink, pszSQL, strlen(pszSQL));
        if (iRetCode != 0)
        {
            char szSqlDump[80];
            apr_snprintf(szSqlDump, sizeof(szSqlDump), "%s", pszSQL);
            apr_snprintf(m_szErrMsg, sizeof(m_szErrMsg),"Fail To Execute SQL[%s]: %s", szSqlDump, mysql_error(m_pstDBLink));
            Close();
            throw CDbException(m_szErrMsg);
        }
    }
    return true;
}

void CMySqlConnection::Close()
{
    if (m_bConnected)
    {
        mysql_close(m_pstDBLink);
        memset(m_pstDBLink, 0, sizeof(MYSQL));
    }
    m_bConnected = false;

}

bool CMySqlConnection::InitDB(const char* pszDBHost, const char* pszUserName, const char* pszPassword, const char* pszDBName, apr_int32_t dwPort, const char *pszCharset, apr_int32_t dwWaitTimeOut)
{
    assert(pszDBHost);
    assert(pszUserName);
    assert(pszPassword);
    assert(pszDBName);

//    apr_int32_t iRetCode;

    if (m_bConnected)
    {
        Close();
    }

    apr_snprintf(m_szDBHost, sizeof (m_szDBHost), "%s", pszDBHost);
    apr_snprintf(m_szUserName, sizeof (m_szUserName), "%s", pszUserName);
    apr_snprintf(m_szPassword, sizeof (m_szPassword), "%s", pszPassword);
    apr_snprintf(m_szDBName, sizeof (m_szDBName), "%s", pszDBName);
    if (pszCharset)
        apr_snprintf(m_szCharsetName, sizeof (m_szCharsetName), "%s", pszCharset);
    m_dwPort = dwPort;
    if(dwWaitTimeOut > 0)
        m_dwWaitTimeOut = dwWaitTimeOut;
    else
        m_dwWaitTimeOut = 0;
    if (Connect())
    {
        m_bConnected = true;
    }

    return m_bConnected;
}

void CMySqlConnection::ExecSQL(const char* pszSQL)
{
    m_szErrMsg[0] = 0;
    if (!m_bConnected)
    {
        if (!ReConnectDB())
            throw CDbException(m_szErrMsg);
    }
    assert(pszSQL);

    apr_int32_t iRetCode = mysql_real_query(m_pstDBLink, pszSQL, strlen(pszSQL));
    if (iRetCode != 0)
    {
        char szSqlDump[80];
        apr_snprintf(szSqlDump, sizeof(szSqlDump), "%s", pszSQL);
        apr_snprintf(m_szErrMsg, sizeof(m_szErrMsg),"Fail To Execute SQL[%s]: %s", szSqlDump, mysql_error(m_pstDBLink));
        apr_uint32_t dwErrNo = mysql_errno(m_pstDBLink);
        if (ER_DUP_ENTRY != dwErrNo && ER_SYNTAX_ERROR != dwErrNo)
            Close();
        throw CDbException(m_szErrMsg);
    }
}
apr_int64_t CMySqlConnection::AffectRow()
{
    assert(m_bConnected);
    m_szErrMsg[0] = 0;
    return mysql_affected_rows(m_pstDBLink);
}

CResultSet *CMySqlConnection::Query(const char* pszSQL)
{
    m_szErrMsg[0] = 0;
    if (!m_bConnected)
    {
        if (!ReConnectDB())
            throw CDbException(m_szErrMsg);
    }

    assert(pszSQL);

    apr_int32_t iRetCode = mysql_real_query(m_pstDBLink, pszSQL, strlen(pszSQL));
    if (iRetCode != 0)
    {
        char szSqlDump[80];
        apr_snprintf(szSqlDump, sizeof(szSqlDump), "%s", pszSQL);
        apr_snprintf(m_szErrMsg, sizeof(m_szErrMsg),"Fail To Query SQL[%s]: %s", szSqlDump, mysql_error(m_pstDBLink));
        Close();
        throw CDbException(m_szErrMsg);
    }
	MYSQL_RES *pstResultSet = mysql_store_result(m_pstDBLink);
    if (pstResultSet == NULL)
    {
        if (mysql_errno(m_pstDBLink)!=0)
        {
            apr_snprintf(m_szErrMsg, sizeof(m_szErrMsg),"Fail To Store Result: %s", mysql_error(m_pstDBLink));
            Close();
            throw CDbException(m_szErrMsg);
        }
        if (strncasecmp(pszSQL, "insert", 6) == 0)
        {
            return NULL;
        }
    
    }
    CMySqlResultSet *poResultSet = new CMySqlResultSet(pstResultSet);
    return poResultSet;
}

apr_uint64_t CMySqlConnection::GetInsertId()
{
    m_szErrMsg[0] = 0;
    assert(m_bConnected);
    return mysql_insert_id(m_pstDBLink);
}

bool CMySqlConnection::IsConnected()
{
    return m_bConnected;
}

void CMySqlConnection::KeepAlive()
{
    m_szErrMsg[0] = 0;
    if (!m_bConnected)
    {
        if (!ReConnectDB())
            throw CDbException(m_szErrMsg);
    }
    mysql_ping(m_pstDBLink);
}

char *CMySqlConnection::EscapeString(char *psToString, apr_uint32_t dwToStringSize, const char* psFromString, apr_uint32_t dwFromStrLength)
{
    assert (dwToStringSize >= dwFromStrLength*2 + 1);
    assert(m_bConnected);
    mysql_real_escape_string(m_pstDBLink, psToString, psFromString, dwFromStrLength);
    return psToString;
}

void CMySqlConnection::DebugInfo()
{
    printf("this:%p\n", this);
}


CTranscation *CMySqlConnection::createTranscation()
{
    CMySqlTranscation *poTran = new CMySqlTranscation((*this));
    return poTran;
}

CMySqlTranscation::CMySqlTranscation(CMySqlConnection& con):m_oConnection(con)
{
    m_bCommited = false;
}

CMySqlTranscation::CMySqlTranscation(const CMySqlTranscation& tran):m_oConnection(tran.m_oConnection)
{
    m_bCommited = tran.m_bCommited;
}

CMySqlTranscation::~CMySqlTranscation()
{
    if (!m_bCommited)
        Rollback();
}

CConnection &CMySqlTranscation::getConnection()
{
    return m_oConnection;
}

void CMySqlTranscation::Begin()
{
    m_oConnection.ExecSQL("SET AUTOCOMMIT=0");
    m_oConnection.ExecSQL("START TRANSACTION");
    m_bCommited = false;
}

void CMySqlTranscation::Commit()
{
    if (m_bCommited)
        return;
    m_oConnection.ExecSQL("COMMIT");
    m_oConnection.ExecSQL("SET AUTOCOMMIT=1");
    m_bCommited = true;
}

void CMySqlTranscation::Rollback()
{
    if (m_bCommited)
        return;
    m_oConnection.ExecSQL("ROLLBACK");
    m_oConnection.ExecSQL("SET AUTOCOMMIT=1");
    m_bCommited = true;
}

